"use client";

import { useDeleteLayers } from "@/hooks/use-delete-layers";
import { useDisableScrollBounce } from "@/hooks/use-disable-scroll-bounce";
import {
  colorToCss,
  connectionIdToColor,
  findIntersectingLayersWithRectangle,
  penPointsToPathLayer,
  pointerEventToCanvasPoint,
  resizeBounds,
} from "@/lib/utils";
import {
  useCanRedo,
  useCanUndo,
  useHistory,
  useMutation,
  useOthersMapped,
  useSelf,
  useStorage,
} from "@/liveblocks.config";
import type { Side, XYWH } from "@/types/canvas";
import {
  Camera,
  CanvasMode,
  CanvasState,
  Color,
  LayerType,
  Point,
} from "@/types/canvas";
import { LiveObject } from "@liveblocks/client";
import { nanoid } from "nanoid";
import { useCallback, useEffect, useMemo, useState } from "react";
import { CursorPresence } from "./cursorPresence";
import { Info } from "./info";
import { LayerPreview } from "./layerPreview";
import { Participants } from "./participants";
import { Path } from "./path";
import { SelectionBox } from "./selectionBox";
import { SelectionTools } from "./selectionTools";
import { Toolbar } from "./toolbar";
// 定义最大图层数量，用于限制可创建的图层数量，避免资源过度使用
const MAX_LAYERS = 100;

// 定义Canvas组件的属性，其中包含一个用于标识画板的属性
interface CanvasProps {
  boardId: string;
}

// Canvas组件，负责处理画布的主要逻辑，包括图层和颜色的选择、画布状态的管理等
export const Canvas = ({ boardId }: CanvasProps) => {
  // 使用useStorage钩子获取图层ID的存储数据
  const layerIds = useStorage((root) => root.layerIds);
  // 使用useSelf钩子获取当前用户的铅笔草稿状态
  const pencilDraft = useSelf((self) => self.presence.pencilDraft);

  // 使用useOthersMapped钩子获取其他用户的选区数据
  const selections = useOthersMapped((other) => other.presence.selection);

  // 使用useMemo钩子计算图层ID到颜色的映射，优化性能，避免不必要的重复计算
  const layerIdsToColorSelection = useMemo(() => {
    const layerIdsToColorSelection: Record<string, string> = {};

    for (const user of selections) {
      const [connectionId, selection] = user;

      for (const layerId of selection) {
        layerIdsToColorSelection[layerId] = connectionIdToColor(connectionId);
      }
    }

    return layerIdsToColorSelection;
  }, [selections]);

  // 使用useState钩子初始化画布的状态，包括模式、相机位置和最后使用的颜色
  const [canvasState, setCanvasState] = useState<CanvasState>({
    mode: CanvasMode.None,
  });
  const [camera, setCamera] = useState<Camera>({ x: 0, y: 0 });
  const [ lastUsedColor, setLastUsedColor] = useState<Color>({
    r: 255,
    g: 255,
    b: 255,
  });

  // 禁用页面滚动弹跳效果，优化用户体验
  useDisableScrollBounce();
  // 获取历史操作数据，用于撤销和重做功能
  const history = useHistory();
  // 获取是否可以撤销操作的状态
  const canUndo = useCanUndo();
  // 获取是否可以重做操作的状态
  const canRedo = useCanRedo();

  // 使用useMutation钩子处理图层的插入操作，参数包括存储对象、当前用户状态和新图层的属性
  const insertLayer = useMutation(
    (
      { storage, setMyPresence },
      layerType:
        | LayerType.Ellipse
        | LayerType.Rectangle
        | LayerType.Text
        | LayerType.Note,
      position: Point
    ) => {
      // 获取当前所有图层的集合
      const liveLayers = storage.get("layers");
      // 如果当前图层数量已经达到上限，则停止创建新图层
      if (liveLayers.size >= MAX_LAYERS) {
        return;
      }

      // 获取当前所有图层ID的集合
      const liveLayerIds = storage.get("layerIds");

      // 生成新图层的唯一ID
      const layerId = nanoid();

      // 创建一个新的LiveObject类型的图层，初始化其属性
      const layer = new LiveObject({
        type: layerType ,
        x: position.x,
        y: position.y,
        width: 100,
        height: 100,
        fill: lastUsedColor,
      });

      // 将新图层的ID添加到图层ID集合中
      liveLayerIds.push(layerId);
      // 将新图层添加到图层集合中
      liveLayers.set(layerId, layer);

      // 更新当前用户的状态，选择新创建的图层，并记录该操作到历史中
      setMyPresence({ selection: [layerId] }, { addToHistory: true });
      // 重置画布模式为无操作模式
      setCanvasState({
        mode: CanvasMode.None,
      });
    },
    [lastUsedColor] // 依赖项列表，当最后使用的颜色变化时，重新计算插入图层的操作
  );
  // 使用useMutation钩子来更新选中的图层的位置
  const translateSelectedLayer = useMutation(
    // 函数参数包括存储对象和当前点的位置
    ({ storage, self }, point: Point) => {
      // 如果当前模式不是平移模式，则不执行任何操作
      if (canvasState.mode !== CanvasMode.Translating) return;

      // 计算当前点与先前记录点的偏移量
      const offset = {
        x: point.x - canvasState.current.x,
        y: point.y - canvasState.current.y,
      };

      // 获取当前所有图层
      const liveLayers = storage.get("layers");

      // 遍历所有选中的图层ID，并更新它们的位置
      for (const id of self.presence.selection) {
        const layer = liveLayers.get(id);
        if (layer) {
          layer.update({
            x: layer.get("x") + offset.x,
            y: layer.get("y") + offset.y,
          });
        }
      }

      // 更新画布状态，设置当前模式为平移模式，并更新当前点的位置
      setCanvasState({
        mode: CanvasMode.Translating,
        current: point,
      });
    },
    // 依赖数组，当canvasState变化时，mutation会重新创建
    [canvasState]
  );

  // 使用useMutation钩子来取消选择当前的图层
  const unselectLayer = useMutation(
    // 函数参数包括self对象和设置我的状态的函数
    ({ self, setMyPresence }) => {
      // 如果有图层被选中，则取消选择并添加到历史记录中
      if (self.presence.selection.length > 0) {
        setMyPresence({ selection: [] }, { addToHistory: true });
      }
    },
    // 依赖数组为空，mutation不会重新创建
    []
  );

  // 使用useMutation钩子来更新选择网络，即通过拖拽形成的选区
  const updateSelectionNet = useMutation(
    // 函数参数包括存储对象，设置我的状态的函数，当前点和起始点的位置
    ({ storage, setMyPresence }, current: Point, origin: Point) => {
      const layers = storage.get("layers").toImmutable();
      // 更新画布状态，设置当前模式为选择网络模式，并更新起始点和当前点的位置
      setCanvasState({
        mode: CanvasMode.SelectionNet,
        origin,
        current,
      });

      // 找到与选择区域相交的图层ID，并更新我的状态中的选择
      const ids = findIntersectingLayersWithRectangle(
        layerIds,
        layers,
        origin,
        current
      );

      setMyPresence({ selection: ids });
    },
    // 依赖数组，当layerIds变化时，mutation会重新创建
    [layerIds]
  );

  // 使用useCallback钩子来优化开始多选的操作
  const startMultiSelection = useCallback(
    // 函数参数为当前点和起始点的位置
    (current: Point, origin: Point) => {
      // 如果当前点和起始点之间的距离大于5，则更新画布状态进入选择网络模式
      if (Math.abs(current.x - origin.x) + Math.abs(current.y - origin.y) > 5) {
        setCanvasState({
          mode: CanvasMode.SelectionNet,
          origin,
          current,
        });
      }
    },
    // 依赖数组为空，回调函数不会因组件重新渲染而改变
    []
  );

  // 使用useMutation钩子来持续绘图，即在使用铅笔工具时追踪笔迹
  const continueDrawing = useMutation(
    // 函数参数包括self对象，设置我的状态的函数，当前点和React的指针事件
    ({ self, setMyPresence }, point: Point, event: React.PointerEvent) => {
      const { pencilDraft } = self.presence;

      // 如果当前模式不是铅笔模式，或者鼠标按钮没有被按下，或者没有铅笔草稿，则不执行任何操作
      if (
        canvasState.mode !== CanvasMode.Pencil ||
        event.buttons !== 1 ||
        !pencilDraft
      )
        return;

      // 更新我的状态，包括光标位置和铅笔草稿
      setMyPresence({
        cursor: point,
        pencilDraft:
          // 如果草稿只有一个点且与当前点相同，则保持草稿不变，否则添加当前点到草稿中
          pencilDraft.length === 1 &&
          pencilDraft[0][0] === point.x &&
          pencilDraft[0][1] === point.y
          ? pencilDraft
          : [...pencilDraft, [point.x, point.y, event.pressure]],
      });
    },
    // 依赖数组，当canvasState.mode变化时，mutation会重新创建
    [canvasState.mode]
  );
// 插入新路径的突变钩子函数
const insertPath = useMutation(
  // 参数是一个函数，用于处理路径插入的逻辑
  ({ storage, self, setMyPresence }) => {
    const liveLayers = storage.get("layers");
    const { pencilDraft } = self.presence;

    // 检查是否满足插入路径的条件
    if (
      !pencilDraft ||
      pencilDraft.length < 2 ||
      liveLayers.size >= MAX_LAYERS
    ) {
      setMyPresence({ pencilDraft: null });
      return;
    }

    // 生成新路径的唯一ID，并在liveLayers中插入新的LiveObject
    const id = nanoid();
    liveLayers.set(
      id,
      new LiveObject(penPointsToPathLayer(pencilDraft, lastUsedColor))
    );

    // 更新layerIds，将新生成的ID加入列表
    const liveLayerIds = storage.get("layerIds");
    liveLayerIds.push(id);

    // 清空铅笔草稿并设置画布状态为铅笔模式
    setMyPresence({ pencilDraft: null });
    setCanvasState({
      mode: CanvasMode.Pencil,
    });
  },
  // 依赖项列表，此处只依赖lastUsedColor
  [lastUsedColor]
);

// 开始绘图的突变钩子函数
const startDrawing = useMutation(
  // 参数包括设置自我状态的函数和绘图的起始点及压力值
  ({ setMyPresence }, point: Point, pressure: number) => {
    setMyPresence({
      pencilDraft: [[point.x, point.y, pressure]],
      pencilColor: lastUsedColor,
    });
  },
  // 依赖项列表，此处依赖lastUsedColor
  [lastUsedColor]
);

// 调整选定图层大小的突变钩子函数
const resizeSelectedLayer = useMutation(
  // 参数包括自我状态和存储对象，以及用于调整大小的点
  ({ self, storage }, point: Point) => {
    if (canvasState.mode !== CanvasMode.Resizing) {
      return;
    }

    // 计算新的边界并更新图层
    const bounds = resizeBounds(
      canvasState.initialBounds,
      canvasState.corner,
      point
    );
    const liveLayers = storage.get("layers");
    const layer = liveLayers.get(self.presence.selection[0]);

    if (layer) {
      layer.update(bounds);
    }
  },
  // 依赖项列表，此处依赖canvasState
  [canvasState]
);

// 处理调整大小句柄指针按下事件的回调函数
const handleResizeHandlePointerDown = useCallback(
  // 参数是调整的角落位置和初始边界
  (corner: Side, initialBounds: XYWH) => {
    history.pause();
    setCanvasState({
      mode: CanvasMode.Resizing,
      initialBounds,
      corner,
    });
  },
  // 依赖项列表，此处依赖history
  [history]
);

// 处理滚轮事件的回调函数，用于调整相机位置
const handleWheel = useCallback((e: React.WheelEvent) => {
  setCamera((camera) => ({
    x: camera.x - e.deltaX,
    y: camera.y - e.deltaY,
  }));
}, []);

// 处理指针移动事件的突变钩子函数
const handlePointerMove = useMutation(
  // 参数包括设置自我状态的函数和指针事件对象
  ({ setMyPresence }, e: React.PointerEvent) => {
    e.preventDefault();

    const current = pointerEventToCanvasPoint(e, camera);

    // 根据当前的画布模式执行相应的操作
    if (canvasState.mode == CanvasMode.Pressing) {
      startMultiSelection(current, canvasState.origin);
    } else if (canvasState.mode === CanvasMode.SelectionNet) {
      updateSelectionNet(current, canvasState.origin);
    } else if (canvasState.mode === CanvasMode.Translating) {
      translateSelectedLayer(current);
    } else if (canvasState.mode === CanvasMode.Resizing) {
      resizeSelectedLayer(current);
    } else if (canvasState.mode === CanvasMode.Pencil) {
      continueDrawing(current, e);
    }

    // 更新自我状态中的光标位置
    setMyPresence({ cursor: current });
  },
  // 依赖项列表，包括各种状态和函数
  [
    canvasState,
    resizeSelectedLayer,
    camera,
    translateSelectedLayer,
    continueDrawing,
    startMultiSelection,
    updateSelectionNet,
  ]
);
// 当指针离开画布时调用的处理函数，用于更新用户的存在状态
const handlePointerLeave = useMutation(({ setMyPresence }) => {
  setMyPresence({ cursor: null });
}, []);

// 处理指针按下事件的函数，用于根据不同的画布模式执行相应的操作
const handlePointerDown = useCallback(
  (e: React.PointerEvent) => {
    // 将指针事件转换为画布上的点
    const point = pointerEventToCanvasPoint(e, camera);

    // 根据当前画布模式决定是否进行绘制或其他操作
    if (canvasState.mode === CanvasMode.Inserting) return;

    if (canvasState.mode === CanvasMode.Pencil) {
      startDrawing(point, e.pressure);
      return;
    }

    // 更新画布状态为按下模式，并记录操作起点
    setCanvasState({
      origin: point,
      mode: CanvasMode.Pressing,
    });
  },
  [camera, canvasState.mode, setCanvasState, startDrawing]
);

// 处理指针抬起事件的函数，根据当前画布模式执行相应的操作
const handlePointerUp = useMutation(
  ({ }, e) => {
    // 将指针事件转换为画布上的点
    const point = pointerEventToCanvasPoint(e, camera);

    // 根据当前画布模式进行相应的处理
    if (
      canvasState.mode === CanvasMode.None ||
      canvasState.mode === CanvasMode.Pressing
    ) {
      unselectLayer();
      setCanvasState({
        mode: CanvasMode.None,
      });
    } else if (canvasState.mode === CanvasMode.Pencil) {
      insertPath();
    } else if (canvasState.mode === CanvasMode.Inserting) {
      insertLayer(canvasState.layerType, point);
    } else {
      setCanvasState({
        mode: CanvasMode.None,
      });
    }
    history.resume();
  },
  [
    camera,
    canvasState,
    history,
    insertLayer,
    unselectLayer,
    setCanvasState,
    insertPath,
  ]
);
// 定义一个用于处理图层指针事件的函数，该函数会在指针按下时调用
// 该函数的目的是在用户开始拖动指针时，更新画布状态，以便进行移动或选择操作
const handleLayerPointerDown = useMutation(
  ({ self, setMyPresence }, e: React.PointerEvent, layerId: string) => {
    // 如果当前模式是铅笔或插入模式，直接返回不处理
    if (canvasState.mode === CanvasMode.Pencil || canvasState.mode === CanvasMode.Inserting) {
      return;
    }

    // 暂停历史记录，阻止事件进一步传播
    history.pause();
    e.stopPropagation();

    // 将指针事件转换为画布坐标
    const point = pointerEventToCanvasPoint(e, camera);

    // 如果当前用户的存在状态中没有选择该图层，则更新选择状态
    if (!self.presence.selection.includes(layerId)) {
      setMyPresence({ selection: [layerId] }, { addToHistory: true });
    }

    // 更新画布状态为移动模式，并记录当前坐标
    setCanvasState({
      mode: CanvasMode.Translating,
      current: point,
    });
  },
  [setCanvasState, camera, history, canvasState.mode]
);

// 定义一个用于删除图层的函数
const deleteLayers = useDeleteLayers();

// 监听键盘事件以支持撤销和重做功能
useEffect(() => {
  function onKeyDown(e: KeyboardEvent) {
    // 根据按键类型执行相应的撤销或重做操作
    switch (e.key) {
      case "z": {
        if (e.ctrlKey || e.metaKey) {
          if (e.shiftKey) {
            history.redo();
          } else {
            history.undo();
          }
          break;
        }
      }
    }
  }

  // 添加键盘事件监听器，并在组件卸载时移除
  document.addEventListener("keydown", onKeyDown);
  return () => {
    document.removeEventListener("keydown", onKeyDown);
  };
}, [history]);

// 渲染主界面，包括信息板、参与者、工具栏等组件
return (
  <main className="h-full w-full relative bg-neutral-100 touch-none">
    <Info boardId={boardId} />
    <Participants />
    <Toolbar
      canvasState={canvasState}
      setCanvasState={setCanvasState}
      undo={history.undo}
      redo={history.redo}
      canUndo={canUndo}
      canRedo={canRedo}
    />
    <SelectionTools camera={camera} setLastUsedColor={setLastUsedColor} />
    <svg
      className="h-[100vh] w-[100vw]"
      onWheel={handleWheel}
      onPointerMove={handlePointerMove}
      onPointerLeave={handlePointerLeave}
      onPointerUp={handlePointerUp}
      onPointerDown={handlePointerDown}
    >
      <g
        style={{
          transform: `translate(${camera.x}px, ${camera.y}px)`,
        }}
      >
        {layerIds.map((layerId) => (
          <LayerPreview
            key={layerId}
            id={layerId}
            onLayerPointerDown={handleLayerPointerDown}
            selectionColor={layerIdsToColorSelection[layerId]}
          />
        ))}
        <SelectionBox
          onResizeHandlePointerDown={handleResizeHandlePointerDown}
        />
        {canvasState.mode === CanvasMode.SelectionNet &&
          canvasState.current && (
            <rect
              className="fill-blue-500/5 stroke-blue-500 stroke-1"
              x={Math.min(canvasState.origin.x, canvasState.current.x)}
              y={Math.min(canvasState.origin.y, canvasState.current.y)}
              width={Math.abs(canvasState.origin.x - canvasState.current.x)}
              height={Math.abs(canvasState.origin.y - canvasState.current.y)}
            />
          )}
        <CursorPresence />
        {pencilDraft && pencilDraft.length > 0 && (
          <Path
            fill={colorToCss(lastUsedColor)}
            points={pencilDraft}
            x={0}
            y={0}
          />
        )}
      </g>
    </svg>
  </main>
  )
};
